#!/bin/sh

# Exit on 1st error
set -e

# NFS exports file.  Some code below keeps a cache of output derived
# from exportfs(8).  When this file is updated the cache is invalid
# and needs to be regenerated.
#
# To change the file, edit the default value below.  Do not set
# CTDB_NFS_EXPORTS_FILE - it isn't a configuration variable, just a
# hook for testing.
nfs_exports_file="${CTDB_NFS_EXPORTS_FILE:-/var/lib/nfs/etab}"

# As above, edit the default value below.  CTDB_NFS_DISTRO_STYLE is a
# test variable only.
nfs_distro_style="${CTDB_NFS_DISTRO_STYLE:-systemd-redhat}"

case "$nfs_distro_style" in
systemd-*)
	# Defaults
	nfs_service="nfs-server"
	nfs_lock_service="rpc-statd"
	nfs_mountd_service="nfs-mountd"
	nfs_status_service="rpc-statd"
	nfs_rquotad_service="rpc-rquotad"
	nfs_config="/etc/sysconfig/nfs"
	nfs_rquotad_config="" # Not use with systemd, restart via service

	case "$nfs_distro_style" in
	*-redhat|*-suse)
		: # Defaults only
		;;
	*-debian)
		nfs_rquotad_service="quotarpc"
		;;
	*)
		echo "Internal error"
		exit 1
	esac
	;;

sysvinit-*)
	# Defaults
	nfs_service="nfs"
	nfs_lock_service=""
	nfs_mountd_service=""
	nfs_status_service=""
	nfs_rquotad_service=""
	nfs_config="/etc/sysconfig/nfs"
	nfs_rquotad_config="$nfs_config"

	case "$nfs_distro_style" in
	*-redhat)
		nfs_lock_service="nfslock"
		;;
	*-suse)
		nfs_service="nfsserver"
		;;
	*-debian)
		nfs_service="nfs-kernel-server"
		nfs_config="/etc/default/nfs-kernel-server"
		nfs_rquotad_config="/etc/default/quota"
		;;
	*)
		echo "Internal error"
		exit 1
	esac
	;;

*)
	echo "Internal error"
	exit 1
esac

# Override for unit testing
if [ -z "$PROCFS_PATH" ] ; then
    PROCFS_PATH="/proc"
fi

##################################################

usage ()
{
    _c=$(basename "$0")
    cat <<EOF
usage: $_c { shutdown | startup }
       $_c { stop | start } { nfs | nlockmgr }
       $_c { monitor-list-shares | monitor-post }
       $_c { register }
EOF
    exit 1
}

##################################################

nfs_load_config ()
{
	_config="${1:-${nfs_config}}"

	if [ -r "$_config" ] ; then
		. "$_config"
	fi
}

##################################################
# Basic service stop and start

basic_stop ()
{
	case "$1" in
	nfs)
		if [ -n "$nfs_rquotad_service" ] ; then
			service "$nfs_rquotad_service" stop
		fi

		service "$nfs_service" stop

		if [ -n "$nfs_lock_service" ] ; then
			service "$nfs_lock_service" stop
		fi
		;;
	nfslock)
		if [ -n "$nfs_lock_service" ] ; then
			service "$nfs_lock_service" stop
		else
			service "$nfs_service" stop
		fi
		;;
	*)
		usage
	esac
}

basic_start ()
{
	case "$1" in
	nfs)
		if [ -n "$nfs_lock_service" ] ; then
			service "$nfs_lock_service" start
		fi

		service "$nfs_service" start

		if [ -n "$nfs_rquotad_service" ] ; then
			service "$nfs_rquotad_service" start
		fi
		;;
	nfslock)
		if [ -n "$nfs_lock_service" ] ; then
			service "$nfs_lock_service" start
		else
			service "$nfs_service" start
		fi
		;;
	*)
		usage
	esac
}

##################################################
# service "stop" and "start" options for restarting

service_stop ()
{
	case "$1" in
	nfs)
		echo 0 >"${PROCFS_PATH}/fs/nfsd/threads"
		basic_stop "nfs" >/dev/null 2>&1 || true
		pkill -9 nfsd
		;;
	nlockmgr)
		basic_stop "nfslock" >/dev/null 2>&1 || true
		;;
	mountd)
		if [ -n "$nfs_mountd_service" ] ; then
			service "$nfs_mountd_service" stop
			return
		fi

		# Default to stopping by hand
		killall -q -9 rpc.mountd
		;;
	rquotad)
		if [ -n "$nfs_rquotad_service" ] ; then
			service "$nfs_rquotad_service" stop
			return
		fi

		# Default to stopping by hand
		killall -q -9 rpc.rquotad
		;;
	status)
		if [ -n "$nfs_status_service" ] ; then
			service "$nfs_status_service" stop
			return
		fi

		# Default to stopping by hand
		killall -q -9 rpc.statd
		;;
	*)
		usage
	esac
}

service_start ()
{
	case "$1" in
	nfs)
		basic_start "nfs"
		;;
	nlockmgr)
		basic_start "nfslock"
		;;
	mountd)
		if [ -n "$nfs_mountd_service" ] ; then
			service "$nfs_mountd_service" start
			return
		fi

		# Default to starting by hand
		nfs_load_config
		if [ -z "$RPCMOUNTDOPTS" ] ; then
			RPCMOUNTDOPTS="${MOUNTD_PORT:+-p }$MOUNTD_PORT"
		fi
		# shellcheck disable=SC2086
		rpc.mountd $RPCMOUNTDOPTS
		;;
	rquotad)
		if [ -n "$nfs_rquotad_service" ] ; then
			service "$nfs_rquotad_service" start
			return
		fi

		# Default to starting by hand
		nfs_load_config "$nfs_rquotad_config"
		if [ -z "$RPCRQUOTADOPTS" ] ; then
			RPCRQUOTADOPTS="${RQUOTAD_PORT:+-p }$RQUOTAD_PORT"
		fi
		# shellcheck disable=SC2086
		rpc.rquotad $RPCRQUOTADOPTS
		;;
	status)
		if [ -n "$nfs_status_service" ] ; then
			service "$nfs_status_service" start
			return
		fi

		# Default to starting by hand
		nfs_load_config
		# Red Hat uses STATDARG, Debian uses STATDOPTS
		opts="${STATDARG:-${STATDOPTS:-''}}"
		if [ -z "$opts" ] ; then
			# shellcheck disable=SC2086
			set -- \
			    ${STATD_HA_CALLOUT:+-H} $STATD_HA_CALLOUT \
			    ${STATD_HOSTNAME:+-n} $STATD_HOSTNAME \
			    ${STATD_PORT:+-p} $STATD_PORT \
			    ${STATD_OUTGOING_PORT:+-o} $STATD_OUTGOING_PORT
			opts="$*"
		fi
		# shellcheck disable=SC2086
		rpc.statd $opts
		;;
	*)
		usage
	esac
}

##################################################
# service init startup and final shutdown

nfs_shutdown ()
{
    basic_stop "nfs"
}

nfs_startup ()
{
    basic_stop "nfs" || true
    basic_start "nfs"
    _f="${PROCFS_PATH}/sys/net/ipv4/tcp_tw_recycle"
    if [ -f "$_f" ] ; then
	    echo 1 >"$_f"
    fi
}

##################################################
# monitor-post support

nfs_check_thread_count ()
{
    # Load NFS configuration to get desired number of threads.
    nfs_load_config

    # If $RPCNFSDCOUNT/$USE_KERNEL_NFSD_NUMBER isn't set then we could
    # guess the default from the initscript.  However, let's just
    # assume that those using the default don't care about the number
    # of threads and that they have switched on this feature in error.
    _configured_threads="${RPCNFSDCOUNT:-${USE_KERNEL_NFSD_NUMBER}}"
    if [ -z "$_configured_threads" ] && type nfsconf >/dev/null 2>&1 ; then
	    _configured_threads=$(nfsconf --get nfsd threads) || true
    fi
    [ -n "$_configured_threads" ] || return 0

    _threads_file="${PROCFS_PATH}/fs/nfsd/threads"

    # nfsd should be running the configured number of threads.  If
    # there are a different number of threads then tell nfsd the
    # correct number.
    read _running_threads <"$_threads_file" || {
	    echo "WARNING: Reading \"${_threads_file}\" unexpectedly failed"
	    exit 0
    }

    # Intentionally not arithmetic comparison - avoids extra errors
    # when above read fails in an unexpected way...
    if [ "$_running_threads" != "$_configured_threads" ] ; then
	echo "Attempting to correct number of nfsd threads from ${_running_threads} to ${_configured_threads}"
	echo "$_configured_threads" >"$_threads_file"
    fi
}

##################################################
# list share directories

nfs_monitor_list_shares ()
{
    _cache_file="${CTDB_NFS_CALLOUT_STATE_DIR}/list_shares_cache"
    # -nt operator is well supported in Linux: dash, bash, ksh, ...
    # shellcheck disable=SC2039
    if  [ ! -r "$nfs_exports_file" ] || [ ! -r "$_cache_file" ] || \
	    [ "$nfs_exports_file" -nt "$_cache_file" ] ; then
	mkdir -p "$CTDB_NFS_CALLOUT_STATE_DIR"
	# We could just use the contents of $nfs_exports_file.
	# However, let's regard that file as internal to NFS and use
	# exportfs, which is the public API.
	if ! _exports=$(exportfs -v) ; then
	    echo "WARNING: failed to run exportfs to list NFS shares" >&2
	    return
	fi

	echo "$_exports" |
	    grep '^/' |
	    sed -e 's@[[:space:]][[:space:]]*[^[:space:]()][^[:space:]()]*([^[:space:]()][^[:space:]()]*)$@@' |
	    sort -u >"$_cache_file"
    fi

    cat "$_cache_file"
}

##################################################

nfs_register ()
{
    cat <<EOF
shutdown
startup
stop
start
monitor-list-shares
monitor-post
EOF
}

##################################################

case "$1" in
    shutdown)
	nfs_shutdown
	;;
    startup)
	nfs_startup
	;;
    stop)
	service_stop "$2"
	;;
    start)
	service_start "$2"
	;;
    monitor-list-shares)
	nfs_monitor_list_shares
	;;
    monitor-post)
	nfs_check_thread_count
	;;
    register)
	nfs_register
	;;
    monitor-pre|releaseip|takeip|releaseip-pre|takeip-pre)
	# Not required/implemented
	:
	;;
    *)
	usage
esac
